import Api from '../request/api.js';
import fs from 'fs';
import {
  md5,
  sleep,
  log,
  randomInt,
  simulateMove,
  subHallName,
} from '../utils/utils.js';
import process from 'process';
import dotenv from 'dotenv';
import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
import { Page, Browser } from 'puppeteer-core';
import dayjs from 'dayjs';
import { PayType, TicketChannel } from '../constant/index.js';

const ua =
  'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1';

// const waitSelectTime = 300000; // 选座等待时间
dotenv.config();
puppeteer.use(StealthPlugin());
const {
  EXECUTABLE_PATH,
  CINEMA_ACC_ID,
  CINEMA_CODE = 'Master',
  CINEMA_TOKEN,
  CINEMA_DATA,
  CINEMA_ACCOUNT,
  CINEMA_PWD,
  CINEMA_CHANNEL,
  ENV,
} = process.env;
const CinemaData = CINEMA_DATA && JSON.parse(CINEMA_DATA);

const API = new Api();

// let isFirst = true;

export default class Yuekeyun {
  /**
   * @type  {Page} page
   */
  page;

  /**
   * @type  {Browser} browser
   */
  browser;

  isFirst = true;

  requestErr = '';

  // cinema_code
  cinemaCode = CINEMA_CODE;

  // token
  loginToken = this.accountLoginToken(CinemaData, CINEMA_TOKEN);

  // 接口请求头
  requestHeader = {
    'User-Agent': ua,
    Accept: 'application/json',
    'Accept-Language':
      'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Content-Type': 'application/x-www-form-urlencoded',
    'gray-lease-code': 'GS',
    accessToken: 'null',
    'Sec-Fetch-Dest': 'empty',
    'Sec-Fetch-Mode': 'cors',
    'Sec-Fetch-Site': 'same-site',
    uaCipher: '',
    Pragma: 'no-cache',
    'Cache-Control': 'no-cache',
  };

  // 请求exbody
  requestbody = {
    empCode: '',
    leaseCode: '',
    channelCode: this.cinemaCode,
    version: 'H5',
    appVersion: 'H5_5.0',
  };

  // 会员卡信息
  vipCardInfo = {
    cardBalance: 0, // 会员卡余额
    cardNo: '', // 会员卡号
    cardName: '', // 会员名称
    vipAmount: 0, // 订单预计消费金额
  };

  // 订单信息
  order = {
    state: '',
    ticketCode: '',
    paramDetail: {}, // 订单详情参数
  };

  // 楼层
  sectionIndex = 0;

  // 影院ID
  cinemaLinkId = 0;
  // 用户TID sid更新使用
  userTid = '';
  userSid = '';
  mobile = '';

  tokenUser = {};

  // 订单ID
  orderId = '';

  // 锁定订单ID
  lockOrderId = '';

  // data
  dataJson = null;

  getPage() {
    return this.page;
  }

  getBrowser() {
    return this.browser;
  }

  setOrderState(state, ticketCode = '', paramDetail = null) {
    if (ticketCode) {
      this.order.ticketCode = ticketCode;
    }
    if (paramDetail) {
      this.order.paramDetail = paramDetail;
    }

    this.order.state = state;

    fs.writeFileSync(
      './orderJson/' + this.dataJson.sn + '.json',
      JSON.stringify(this.order, null, 2),
      {
        flag: 'w',
      },
      (err) => {
        if (err) {
          console.error('订单状态写入', err);
        }
      }
    );
  }

  // 判断是否已经出票
  async isTicket(dataJson) {
    const filePath = './orderJson/' + dataJson.sn + '.json';
    if (fs.existsSync(filePath)) {
      // 存在说明已经出过票
      const buffer = fs.readFileSync(filePath);
      let ticketCode = '';
      const fileContent = buffer.toString();
      let paramDetail = {};
      if (fileContent) {
        const orderState = JSON.parse(fileContent);
        if (orderState.state === 'paySuc') {
          paramDetail = orderState.paramDetail;
          // 支付成功, 但是获取详情失败
          const orderDetail = await this.requestPageApi(
            'mtop.alipic.lark.own.order.getOrderDetail',
            paramDetail.paramDetail
          ).then((res) => res && res.ticketInfo);
          if (!orderDetail) {
            throw new Error('获取订单详情失败[RETRY]');
          }
          ticketCode = orderDetail.confirmationId;
          this.setOrderState('success', ticketCode, paramDetail);
        } else {
          ticketCode = orderState.ticketCode;
        }
      }
      return { payType: paramDetail.payType || 0, ticketCode };
    }

    return false;
  }

  // 订单开始 初始化
  async orderStart(dataJson) {
    // 检查登录
    await this.checkLogin({
      cinemaAccId: CINEMA_ACC_ID,
      cinemaCode: CINEMA_CODE,
      cinemaAccount: CINEMA_ACCOUNT,
      cinemaAccountPwd: CINEMA_PWD,
    });

    this.order = {
      state: '',
      ticketCode: '',
      paramDetail: {},
    };
    this.vipCardInfo = {
      cardBalance: 0, // 会员卡余额
      cardNo: '', // 会员卡号
      cardName: '',
      vipAmount: 0, // 订单预计消费金额
    };
    this.sectionIndex = dataJson.sectionId - 1;
    this.dataJson = dataJson;
    this.lockOrderId = '';
    this.orderId = '';
  }

  async init() {
    // 初始化
    this.browser = await puppeteer.launch({
      executablePath: EXECUTABLE_PATH,
      //设置超时时间
      timeout: 15000,
      ignoreHTTPSErrors: true,
      devtools: ENV === 'local',
      headless: ENV === 'local' ? false : 'new',
      args: [
        // '--disable-gpu',
        // '--disable-setuid-sandbox',
        // '--no-first-run',
        // '--no-zygote',
        // '--single-process',
        // '--disable-dev-shm-usage',
        '--no-sandbox',
        '--disable-plugins',
        '--disable-setuid-sandbox',
      ],
    });
    // this.page = await this.browser.newPage();
    const pages = await this.browser.pages();
    this.page = pages[0];
    await this.page.setGeolocation({ latitude: 31.23, longitude: 121.47 });
    await this.page.setUserAgent(ua);
    await this.page.setViewport({ width: 375, height: 700 });
  }

  // 登录
  async login(loginToken = this.loginToken, code = CINEMA_CODE) {
    // await sleep(200);
    // 打开首页
    // log('登录开始 '+ code + ' 打开页面：' + `https://h5lark.yuekeyun.com/location/index.html?wapid=${code}`);
    // await this.page.goto(
    //   `https://h5lark.yuekeyun.com/location/index.html`,
    //   {
    //     waitUntil: 'networkidle2',
    //     timeout: 10000,
    //   }
    // );
    if (this.isFirst) {
      this.isFirst = false;
      await this.page.goto(`https://h5lark.yuekeyun.com/location/index.html`, {
        waitUntil: 'networkidle2',
        timeout: 10000,
      });
    }
    if (loginToken) {
      this.setLoginKey(loginToken);

      // 协议弹窗
      // https://mtop.yuekeyun.com/h5/mtop.alipic.lark.own.lease.channelagreement/1.0/?jsv=2.6.0&appKey=12574478&t=1695998335560&sign=222426a11986317d342ed88f2188cc98&api=mtop.alipic.lark.own.lease.channelAgreement&v=1.0&type=originaljson&timeout=20000&dataType=json
      const agreeData = await this.requestPageApi(
        'mtop.alipic.lark.own.lease.channelAgreement',
        {
          type: 'PERSONAL',
          channelCode: code,
        }
      );
      const alertItem = (agreeData || []).find((i) => i.alert);

      let agreeVer = '';
      if (alertItem) {
        log('协议弹窗数据: ' + JSON.stringify(agreeData));
        agreeVer = alertItem['version'];
      }
      //   log('协议版本:' + agreeVer);
      await this.page.evaluate(
        (key, val, agreeVer) => {
          localStorage.setItem(key, val);
          agreeVer && localStorage.setItem('historyVersion', agreeVer);
        },
        code + 'TOcookie_objBase',
        loginToken,
        agreeVer
      );
    }
  }

  // 设置登录态的键值
  setLoginKey(loginToken) {
    try {
      const tokenObj = JSON.parse(loginToken);
      this.cinemaLinkId = tokenObj.cinema.linkid;
      this.userTid = tokenObj.user.tid;
      this.mobile = tokenObj.phone;
      this.userSid = tokenObj.user.sid;
      this.tokenUser = tokenObj.user;
    } catch (_e) {
      log('登录token解析失败', 'error');
    }
  }

  // 登录
  async checkLogin(user, times = 1) {
    // 确认登录态是否生效
    // 跳转到我的页面
    await this.page.goto(
      `https://h5lark.yuekeyun.com/user/index.html?wapid=${
        user.cinemaCode
      }&stamp=${Date.now()}&spm=a2of6.film_list_page.0.0`,
      {
        waitUntil: 'networkidle2',
      }
    );

    const tologinText = await this.page
      .$eval('.tologin', (el) => el && el.textContent)
      .catch(() => null);

    // const tologin = await this.page.$('.tologin');
    log('登录态检查 ' + user.cinemaAccount + ' ' + tologinText);
    if ((tologinText && tologinText.includes('登录')) || !this.userSid) {
      log('登录态失效 ' + JSON.stringify(user));
      const loginObjStr = await this.handleLogin(user).catch((e) => {
        const screenshotPath =
          './screenshot/error_' + user.cinemaAccount + '.png';
        this.page.screenshot({
          path: screenshotPath,
        });
        log('自动登录异常 ' + user.cinemaAccount + '\n' + e.stack, 'error');
        return null;
      });
      const loginObj = JSON.parse(loginObjStr);
      if (!loginObj || !loginObj.user || !loginObj.user.account) {
        if (times === 1) {
          // 再尝试一次
          return this.checkLogin(user, 2);
        }
        log('自动登录处理失败,登录用户:' + JSON.stringify(user), 'error');
        // 退出程序 人工处理
        await API.authExpire(user.cinemaAccId);
        if (CINEMA_CHANNEL == TicketChannel.TASK) {
          return false;
        }
        await this.browser.close();
        process.exit(0);
      }
    }
    return true;
  }

  // 执行登录
  //https://h5lark.yuekeyun.com/login/password.html?wapid=TSDYY_H5_PROD_12576_MPS&returnUrl=https://h5lark.yuekeyun.com/user/index.html&stamp=1684996758375&spm=a2of6.auth_login_page.0.0
  async handleLogin(user) {
    await this.page.goto(
      `https://h5lark.yuekeyun.com/login/password.html?wapid=${
        user.cinemaCode
      }&returnUrl=https://h5lark.yuekeyun.com/user/index.html&stamp=${Date.now()}&spm=a2of6.auth_login_page.0.0`,
      {
        timeout: 5 * 1000,
        waitUntil: 'networkidle0',
      }
    );

    await this.page.waitForSelector('input[type=text]', { timeout: 2000 });

    await this.page.focus('input[type=text]');
    await sleep(50);
    await this.page.type('input[type=text]', user.cinemaAccount, {
      delay: 50,
    });

    await this.page.focus('input[type=password]');
    await sleep(50);
    await this.page.type('input[type=password]', user.cinemaAccountPwd, {
      delay: 50,
    });

    await this.page.click('.btn-bp1');

    // 等待滑块出现
    var slide_btn = await this.page
      .waitForSelector('.slider', { timeout: 2000 })
      .catch((e) => {
        log('滑块验证码不存在', 'error');
        console.error(e);
        return null;
      });
    if (!slide_btn) {
      await this.page.screenshot({
        path: `./screenshot/loginError${user.cinemaCode}_${user.cinemaAccount}.png`,
      });
      return null;
    }
    // 计算滑块距离
    const rect = await this.page.evaluate((slide_btn) => {
      const { top, left, bottom, right } = slide_btn.getBoundingClientRect();
      return { top, left, bottom, right };
    }, slide_btn);
    rect.left = rect.left + 10;
    rect.top = rect.top + 10;
    const mouse = this.page.mouse;

    await mouse.move(rect.left, rect.top, { steps: 1 });
    // 关键点2
    await this.page.touchscreen.tap(rect.left, rect.top); // h5需要手动分发事件 模拟app的事件分发机制。
    await mouse.down();

    let number = randomInt(400, 800);

    await simulateMove(
      this.page,
      rect.left,
      rect.top,
      rect.right + number,
      rect.bottom,
      { steps: 1 }
    );

    await this.page.touchscreen.tap(rect.right + number, rect.bottom);
    await mouse.up();

    // 登录成功
    log('自动登录成功 ' + user.cinemaAccount);
    await sleep(2000);

    // token
    this.loginToken = await this.page.evaluate(
      (key) => localStorage.getItem(key),
      user.cinemaCode + 'TOcookie_objBase'
    );

    this.setLoginKey(this.loginToken);

    await API.updateLoginToken(
      user.cinemaAccId,
      JSON.stringify(this.tokenUser)
    );

    return this.loginToken;
  }

  async selectShow() {
    // 解锁请求
    const bizData = await this.requestPageApi(
      'mtop.alipic.lark.own.schedule.getSchedules',
      {
        cinemaLinkId: this.cinemaLinkId,
      }
    );
    if (!bizData) {
      return false;
    }

    // 获取场次
    const showTimestamp = this.dataJson.showTimestamp;
    const showList = [];
    for (const item of bizData.films) {
      if (this.dataJson.filmName !== item.filmName) {
        continue;
      }

      for (const dateMap of item.dates) {
        for (const show of dateMap.schedules) {
          if (Number(show.showTime) !== showTimestamp) {
            continue;
          }
          showList.push(show);
        }
      }
    }
    if (showList.length === 0) {
      return false;
    }
    if (showList.length > 1) {
      // 选择最早的场次
      const hn = subHallName(this.dataJson.hallName);
      for (const show of showList) {
        if (hn === subHallName(show.hallName)) {
          return show;
        }
      }
    }
    return showList[0];
  }

  // 获取座位信息
  async getSeat(selectShowRes) {
    // 等待选座
    const dataJson = this.dataJson;
    // 解锁请求
    const bizData = await this.requestPageApi(
      'mtop.alipic.lark.own.seat.getSeatMap',
      {
        cinemaLinkId: this.cinemaLinkId,
        hallId: selectShowRes.hallId,
        scheduleId: selectShowRes.scheduleId,
        scheduleKey: selectShowRes.scheduleKey,
        apiVersion: '1.0',
      }
    );
    if (!bizData || !bizData.sections) {
      log(
        '场次座位表获取失败 ' +
          ' sid:' +
          this.userSid +
          ' ' +
          JSON.stringify(this.loginToken),
        'error'
      );
      throw new Error('场次座位表获取失败[RETRY]');
    }

    // 获取场次
    const seatList = [];
    const { seats } = bizData.sections[this.sectionIndex];
    // 座位转成 [[11,2],[11,3]]
    const seatListArr = this.dataJson.seatList.map((i) =>
      i.match(/[\d|A-Za-z]+/g).map((d) => (isNaN(Number(d)) ? d : Number(d)))
    );
    for (const seat of seats) {
      const { rowName, columnName } = seat;
      //   const seatNameArr = seat.name
      //     .match(/\d+/g)
      //     .map((d) => (isNaN(Number(d)) ? d : Number(d)));
      for (const item of seatListArr) {
        if (rowName == item[0] && columnName == item[1]) {
          if (seat.status != 1) {
            throw new Error(
              `座位已被锁定 ${this.dataJson.from} ${this.dataJson.cinemaName}`
            );
          }
          seatList.push(seat);
        }
      }
    }
    if (seatList.length < dataJson.seatList.length) {
      throw new Error('座位未找到');
    }
    return seatList;
  }

  // 锁定座位
  /**
   * [{"areaId":"0","column":3,"columnName":"2","flag":0,"name":"1排2座","row":1,"rowName":"1","seatId":"000000031026-1-3","status":1}]
   */
  async lockSeat(showInfo, seatList) {
    // 锁定请求
    const lockBizData = await this.requestPageApi(
      'mtop.alipic.lark.own.seat.lockSeats',
      {
        seatIds: seatList.map((seat) => seat.seatId).join('|'),
        cinemaLinkId: this.cinemaLinkId,
        scheduleId: showInfo.scheduleId,
        scheduleKey: showInfo.scheduleKey,
      }
    );
    if (lockBizData && lockBizData.lockOrderId) {
      // 成功锁座
      log('座位锁定成功 ' + JSON.stringify(lockBizData));
      this.lockOrderId = lockBizData.lockOrderId;
    } else {
      // 锁座失败
      log('座位锁定请求失败 ' + JSON.stringify(lockBizData), 'error');
      throw new Error('锁座失败[RETRY]');
    }
  }

  // 获取订单信息
  async submitOrderInfo(showInfo, seatList, cinemaInfo) {
    log(
      '提交订单 ' + JSON.stringify(showInfo) + ' ' + JSON.stringify(cinemaInfo)
    );
    // 获取会员&优惠券信息
    const privilegeBizData = await this.requestPageApi(
      'mtop.alipic.lark.own.pay.getPayPrivilegeInfo',
      {
        cinemaLinkId: this.cinemaLinkId,
        scheduleId: showInfo.scheduleId,
        scheduleKey: showInfo.scheduleKey,
        hallId: showInfo.hallId,
        seatIds: seatList.map((seat) => seat.seatId).join('|'),
      }
    );
    log('获取会员&优惠券信息 ' + JSON.stringify(privilegeBizData));
    if (!privilegeBizData) {
      throw new Error('获取会员&优惠券信息失败[RETRY]');
    }
    let payInfo;
    let payType;
    if (cinemaInfo.settle_type == PayType.BALANCE) {
      // 使用会员卡余额
      payInfo = await this.useVipBalance(privilegeBizData, cinemaInfo);
      payType = PayType.BALANCE;
    } else if (
      cinemaInfo.settle_type == PayType.E_TICKET ||
      cinemaInfo.settle_type == PayType.COUPON
    ) {
      // 仅使用优惠券 (电子券)
      payInfo = await this.useCoupon(
        privilegeBizData,
        seatList,
        showInfo,
        cinemaInfo
      );
      payType = PayType.E_TICKET;
    } else {
      // 哪个成本低用哪个
      let vipPayInfo = null;
      let couponPayInfo = null;
      let eTicketPayInfo = null;
      if (cinemaInfo.vip_cost_ratio > 0) {
        vipPayInfo = await this.useVipBalance(
          privilegeBizData,
          cinemaInfo
        ).catch(() => null);
      }
      if (cinemaInfo.coupon_fee > 0 || cinemaInfo.e_ticket_fee > 0) {
        couponPayInfo = await this.useCoupon(
          privilegeBizData,
          seatList,
          showInfo,
          cinemaInfo
        ).catch(() => null);
      }
      log(
        '使用会员&优惠券信息 ' +
          JSON.stringify(vipPayInfo) +
          ' ' +
          JSON.stringify(couponPayInfo)
      );
      if (!vipPayInfo && !couponPayInfo) {
        throw new Error('无法选择支付方式');
      }

      let vipCost = 999999999;
      let couponCost = 999999999;
      if (vipPayInfo) {
        vipCost = vipPayInfo.cost;
      }
      if (couponPayInfo) {
        couponCost = couponPayInfo.cost;
      }

      // 支付方式 1优惠券 2vip 3券+vip
      if (vipCost <= couponCost) {
        payInfo = vipPayInfo;
        payType = PayType.BALANCE;
      } else {
        payInfo = couponPayInfo;
        payType = cinemaInfo.coupon_fee > 0 ? PayType.COUPON : PayType.E_TICKET;
      }
      log(`VIP成本:${vipCost} 优惠券成本:${couponCost} 类型:${payType}`);
    }

    log('更新成本 ' + JSON.stringify(payInfo));
    // 更新成本
    await API.updateOrderCost(this.dataJson.id, payInfo.cost / 100);

    // 判定成本与报价
    const sellAmount = this.dataJson.sellAmount * 100;
    log('判定成本与报价 ' + sellAmount + ' ' + payInfo.cost);
    // force_ticket是否强制出票
    if (
      sellAmount > 0 &&
      sellAmount <= payInfo.cost &&
      !cinemaInfo.force_ticket
    ) {
      throw new Error(
        `成本高于报价[类型${payType}-成本${payInfo.cost}-报价${sellAmount}]`
      );
    }

    // 创建订单
    const payCost = payInfo.cost;
    delete payInfo.cost;
    log(
      '创建订单参数 ' +
        JSON.stringify({
          ...payInfo,
          mobile: this.mobile,
          scheduleId: showInfo.scheduleId,
          scheduleKey: showInfo.scheduleKey,
          cinemaLinkId: this.cinemaLinkId,
          lockOrderId: this.lockOrderId,
        })
    );
    // https://mtop.yuekeyun.com/h5/mtop.alipic.lark.own.order.createticketorder/1.0/?jsv=2.6.0&appKey=12574478&t=1751954501831&sign=4ed2b73650968264492d9ff6bdf98fb1&api=mtop.alipic.lark.own.order.createTicketOrder&v=1.0&type=originaljson&timeout=20000&dataType=json
    const createTicketOrder = await this.requestPageApi(
      'mtop.alipic.lark.own.order.createTicketOrder',
      {
        ...payInfo,
        mobile: this.mobile,
        scheduleId: showInfo.scheduleId,
        scheduleKey: showInfo.scheduleKey,
        cinemaLinkId: this.cinemaLinkId,
        lockOrderId: this.lockOrderId,
      }
    );
    log('创建订单 ' + JSON.stringify(createTicketOrder));
    if (!createTicketOrder) {
      throw new Error('订单创建失败[TRY]');
    }

    // 设置订单ID 用户订单回滚
    this.orderId = createTicketOrder.orderId;

    if (createTicketOrder.payMethod === 'CARD') {
      // 会员卡支付需要密码
      const payCard = await this.requestPageApi(
        'mtop.alipic.lark.own.card.payOrderWithCard',
        {
          cinemaLinkId: this.cinemaLinkId,
          orderId: createTicketOrder.orderId,
          orderType: createTicketOrder.orderType,
          cardNumber: createTicketOrder.cardNumber,
          cardPassword: cinemaInfo.vip_pwd,
          cardCinemaLinkId: this.cinemaLinkId,
        }
      );
      log('会员卡支付 ' + JSON.stringify(payCard));
      if (!payCard) {
        throw new Error('订单支付失败[TRY]');
      }

      // 更新余额
      await API.updateVipBalance(
        (
          (this.vipCardInfo.cardBalance - this.vipCardInfo.vipAmount) /
          100
        ).toFixed(2),
        createTicketOrder.cardNumber,
        this.vipCardInfo.cardName || '会员卡Z'
      ).catch((e) => {
        log('更新会员卡余额失败：' + JSON.stringify(e));
      });
      log('更新会员卡余额 ' + JSON.stringify(this.vipCardInfo));
    }

    // 设置订单状态
    const paramDetail = {
      cinemaLinkId: this.cinemaLinkId,
      orderId: createTicketOrder.orderId,
      orderType: createTicketOrder.orderType,
      needMatchConsumeGift: false,
    };

    // 写入订单状态防止出现异常
    this.setOrderState('paySuc', '', {
      payType,
      paramDetail,
    });

    log('获取订单详情 ' + JSON.stringify(paramDetail));
    const orderDetail = await this.requestPageApi(
      'mtop.alipic.lark.own.order.getOrderDetail',
      paramDetail
    ).then((res) => res && res.ticketInfo);
    if (!orderDetail) {
      throw new Error('获取订单详情失败[RETRY]');
    }

    // 设置订单状态
    this.setOrderState('success', orderDetail.confirmationId, {
      payType,
      paramDetail,
    });

    orderDetail['payCost'] = payCost;
    orderDetail['payType'] = payType;
    return orderDetail;
  }

  // 获取订单原始金额
  getOriginPrice(privilegeBizData) {
    return privilegeBizData.privileges.find(
      (p) => p.privilegeType === 'ORIGINAL_PRICE'
    )['privilegeTotalPrice'];
  }

  getOrderCoupon() {
    const commonCoupon = [];
    const payments = [];
    let countCouponNum = 0;
    for (const coupon of sortArr) {
      // 出票需要额外的券
      let extened = 0;
      if (commonCoupon.length < length) {
        // 是否需要选择多张
        if (coupon.exchangeQuantity - 1 > 0) {
          if (extened === 0) {
            extened = coupon.exchangeQuantity - 1;
            length = length + extened;
          } else {
            extened = extened - 1;
          }
        }
        if (coupon.concreteProductType === 'COMMON') {
          // 通用券必须标注使用方式
          commonCoupon.push({
            couponCode: coupon.couponCode,
            concreteProductType: 'TICKET',
          });

          payments.push({
            payMethod: 'COUPON',
            couponCodeParams: `${coupon.couponCode}-${coupon.couponType}-TICKET`,
          });
        } else {
          commonCoupon.push({
            couponCode: coupon.couponCode,
          });

          payments.push({
            payMethod: 'COUPON',
            couponCodeParams: `${coupon.couponCode}-${coupon.couponType}`,
          });
        }
      } else {
        countCouponNum += 1;
      }
    }

    return { payments, commonCoupon };
  }

  // 使用优惠券
  async useCoupon(privilegeBizData, seatList, selectShowRes, cinemaInfo) {
    const { preferCouponInfo = {} } = privilegeBizData;
    const { canUseCoupon, preferCoupons = [] } = preferCouponInfo;
    if (!canUseCoupon) {
      // 优惠券开关关闭
      throw new Error('不能使用优惠券[RETRY]');
    }

    let { length } = seatList;
    const commonCoupon = [];
    const payments = [];
    let countCouponNum = 0;
    // 过滤加排序
    const couponListArr = preferCoupons.filter(
      (coupon) =>
        coupon.state === 'USEFUL' &&
        coupon.useType === 'TICKET' &&
        (coupon.couponType === 'EXCHANGE_VOUCHER' ||
          coupon.couponType === 'REPRICE_COUPON')
    );
    const sortArr = couponListArr
      .sort((a, b) => a.expireTime - b.expireTime) // 过期时间从小到大排序
      .sort((a, b) => b.discountValue - a.discountValue); // 折扣值从大到小排序
    // 将券分类
    const couponTypeMap = {};
    for (const coupon of sortArr) {
      if (!couponTypeMap[coupon.templateCode]) {
        couponTypeMap[coupon.templateCode] = [];
      }
      couponTypeMap[coupon.templateCode].push(coupon);
    }
    // 分类之后还需要按折扣值从大到小排序
    const couponTypeArr = Object.values(couponTypeMap).sort(
      (a, b) => b[0].discountValue - a[0].discountValue
    );

    for (const coupons of couponTypeArr) {
      if (coupons.length < length) {
        continue;
      }
      for (const coupon of coupons) {
        // 出票需要额外的券
        let extened = 0;
        if (commonCoupon.length < length) {
          // 是否需要选择多张
          if (coupon.exchangeQuantity - 1 > 0) {
            if (extened === 0) {
              extened = coupon.exchangeQuantity - 1;
              length = length + extened;
            } else {
              extened = extened - 1;
            }
          }
          if (coupon.concreteProductType === 'COMMON') {
            // 通用券必须标注使用方式
            commonCoupon.push({
              couponCode: coupon.couponCode,
              concreteProductType: 'TICKET',
            });

            payments.push({
              payMethod: 'COUPON',
              couponCodeParams: `${coupon.couponCode}-${coupon.couponType}-TICKET`,
            });
          } else {
            commonCoupon.push({
              couponCode: coupon.couponCode,
            });

            payments.push({
              payMethod: 'COUPON',
              couponCodeParams: `${coupon.couponCode}-${coupon.couponType}`,
            });
          }
        } else {
          countCouponNum += 1;
        }
      }
    }

    if (length > commonCoupon.length) {
      throw new Error('优惠券不足');
    }

    // 查询券优惠
    const queryDiscount = await this.requestPageApi(
      'mtop.alipic.lark.own.pay.getPayDiscountPrice',
      {
        couponCodes: commonCoupon.map((c) => c.couponCode).join('|'),
        seatIds: seatList.map((seat) => seat.seatId).join('|'),
        commonCouponJson: JSON.stringify(commonCoupon),
        cinemaLinkId: this.cinemaLinkId,
        scheduleId: selectShowRes.scheduleId,
        scheduleKey: selectShowRes.scheduleKey,
      }
    );
    if (!queryDiscount) {
      log('++++++++凤凰优惠券 ' + JSON.stringify(couponTypeMap));
      throw new Error('优惠券折扣查询失败');
    }

    // 会员卡余额补充
    const privilegeTotalPrice = Number(queryDiscount.privilegeTotalPrice);
    if (privilegeTotalPrice !== 0) {
      if (!privilegeBizData.cards || !Array.isArray(privilegeBizData.cards)) {
        throw new Error('需要补充付款,但未找到可支付会员卡');
      }

      let getCard = false;
      for (const card of privilegeBizData.cards) {
        if (
          card.canChargeFlg === 'Y' &&
          Number(card.balance) >=
            privilegeTotalPrice + Number(cinemaInfo.frozen_balance)
        ) {
          getCard = true;
          payments.push({
            payMethod: 'CARD',
            payCardNumber: card['cardNumber'],
          });
          break;
        }
      }
      if (!getCard) {
        throw new Error('券抵扣余额不足');
      }
    }

    let cost = 9999999;
    if (cinemaInfo.coupon_fee > 0) {
      cost = cinemaInfo.coupon_fee * commonCoupon.length * 100;
      if (privilegeTotalPrice > cost) {
        throw new Error(
          `补充金额大于预设成本bc${privilegeTotalPrice}-cost${cost}`
        );
      }
      // 更新券数
      await API.updateCouponNum(countCouponNum);
    } else {
      cost =
        privilegeTotalPrice +
        cinemaInfo.e_ticket_fee * commonCoupon.length * 100;
      // 更新券数
      await API.updateETicketNum(countCouponNum);
    }
    // 上传券
    const vercodes = commonCoupon.map((c) => c.couponCode);
    await API.uploadCoupon(
      couponListArr
        .filter((i) => !vercodes.includes(i.couponCode))
        .map((coupon) => ({
          type: 1,
          couponNo: coupon.couponCode,
          status: coupon.state === 'USEFUL' ? 1 : 2,
          timeStart: coupon.startTime / 1000,
          timeEnd: coupon.expireTime / 1000,
          couponVer: coupon.templateCode || '',
          couponName: coupon.name || '',
          couponInfo: coupon.policyDesc || '',
        })),
      CINEMA_ACC_ID,
      CINEMA_ACCOUNT,
      TicketChannel.PHOENIX
    );

    // 优惠券可以折扣全部金额
    return {
      totalPrice: this.getOriginPrice(privilegeBizData),
      payAmount: privilegeTotalPrice,
      cost, // 成本
      tickets: JSON.stringify(
        seatList.map((seat) => ({ seatId: seat.seatId }))
      ),
      payments: JSON.stringify(payments),
    };
  }

  // 使用VIP余额
  async useVipBalance(privilegeBizData, cinemaInfo) {
    if (!privilegeBizData.cards) {
      log('获取会员卡列表失败 ' + JSON.stringify(privilegeBizData));
      throw new Error('获取会员卡列表失败[RETRY]');
    }
    // 使用会员卡余额
    const cardMap = {}; // 会员卡Map
    for (const card of privilegeBizData.cards) {
      if (card.canChargeFlg === 'N') {
        continue;
      }
      cardMap[card['cardNumber']] = card;
    }

    for (const privilege of privilegeBizData.privileges) {
      // 会员价
      this.vipCardInfo.vipAmount = Number(privilege.privilegeTotalPrice);
      if (!privilege['cardInfos'] || privilege['cardInfos'].length === 0) {
        // 大概率是原价卡
        continue;
      }

      let cardDetail = null;
      for (const cardInfo of privilege['cardInfos']) {
        const cd = cardMap[cardInfo['cardNumber']];
        if (!cd) {
          continue;
        }

        // 更新余额
        this.vipCardInfo.cardBalance = Number(cd['balance']);
        this.vipCardInfo.cardName = cd['cardName'];
        await API.updateVipBalance(
          this.vipCardInfo.cardBalance / 100,
          cd['cardNumber'],
          cd['cardName'] || '会员卡X',
          0
        ).catch((e) => {
          log('更新会员卡余额失败：' + JSON.stringify(e));
        });
        if (
          this.vipCardInfo.vipAmount + Number(cinemaInfo.frozen_balance) >
          this.vipCardInfo.cardBalance
        ) {
          // 票价金额+冻结金额 > 卡余额
          continue;
        }
        cardDetail = cd;
        break;
      }

      if (!cardDetail) {
        continue;
      }

      let totalPrice = 0;
      let payAmount = 0;
      let seatIdList = [];
      for (const tick of privilege.ticketInfos) {
        totalPrice += Number(tick.ticketOriginalPrice);
        payAmount += Number(tick.ticketPrivilegePrice);
        if (tick.activityId) {
          seatIdList.push({
            seatId: tick.seatId,
            activityId: tick.activityId,
          });
        } else {
          seatIdList.push({
            seatId: tick.seatId,
          });
        }
      }

      return {
        totalPrice,
        payAmount,
        cost: payAmount * cinemaInfo.vip_cost_ratio, // 成本
        tickets: JSON.stringify(seatIdList),
        payments: JSON.stringify([
          {
            payMethod: 'CARD',
            payCardNumber: cardDetail['cardNumber'],
          },
        ]),
      };
    }

    throw new Error('Vip卡余额不足');
  }

  // 解锁座位
  async unlockSeat() {
    if (this.lockOrderId) {
      // 解锁请求
      return await this.requestPageApi(
        'mtop.alipic.lark.own.seat.unlockSeats',
        {
          lockOrderId: this.lockOrderId,
          cinemaLinkId: this.cinemaLinkId,
        }
      );
    }
    return true;
  }

  // 订单取消
  async cancelOrder() {
    if (this.orderId) {
      // 解锁请求
      return await this.requestPageApi(
        'mtop.alipic.lark.own.order.cancelOrder',
        {
          orderId: this.orderId,
          cinemaLinkId: this.cinemaLinkId,
          orderType: 'TICKET',
        }
      );
    }
    return true;
  }

  // 页面接口请求
  async requestPageApi(path, data, first = true) {
    const token = await this.page.evaluate(
      () =>
        document.cookie.match(/_m_h5_tk\=(.+?)_/) &&
        document.cookie.match(/_m_h5_tk\=(.+?)_/)[1]
    );
    let postData = {
      ...this.requestbody,
      ...data,
    };
    if (this.userSid) {
      postData.larkSid = this.userSid;
    }
    postData = JSON.stringify(postData);

    const timestamp = Date.now();
    const appKey = 12574478;
    const sign = md5(token + '&' + timestamp + '&' + appKey + '&' + postData);
    let url = `https://mtop.yuekeyun.com/h5/${path.toLocaleLowerCase()}/1.0/?jsv=2.6.0&appKey=${appKey}&t=${timestamp}&sign=${sign}&api=${path}&v=1.0&type=originaljson&timeout=20000&dataType=json`;
    // https://mtop.yuekeyun.com/h5/mtop.alipic.lark.own.seat.lockseats/1.0/?jsv=2.6.0&appKey=12574478&t=1679976426491&sign=cd6e63655922343dd2d2be34cbdd43dd&api=mtop.alipic.lark.own.seat.lockSeats&v=1.0&type=originaljson&timeout=20000&dataType=json
    const res = await this.page.evaluate(
      (url, requestHeader, data) => {
        //人机识别模块，只需初始化一次
        if (typeof AWSC !== 'undefined' && AWSC !== null) {
          let uabModule;
          let webUmidToken;
          AWSC.use('uab', function (state, uab) {
            if (state === 'loaded') {
              uabModule = uab;
            }
          });
          //设备指纹模块，得到设备token，只需初始化一次
          AWSC.use('um', function (state, um) {
            if (state === 'loaded') {
              um.init(
                {
                  appName: 'lark-cinemaprod',
                },
                function (initState, result) {
                  if (initState === 'success') {
                    webUmidToken = result.tn;
                  }
                }
              );
            }
          });
          try {
            requestHeader['bx-ua'] = uabModule.getUA();
            requestHeader['bx-umidtoken'] = webUmidToken;
          } catch (_e) {}
        }
        return fetch(url, {
          method: 'POST',
          credentials: 'include',
          mode: 'cors',
          body: 'data=' + encodeURIComponent(data),
          headers: requestHeader,
        })
          .then((res) => res.json())
          .catch((e) => log('请求接口失败 ' + JSON.stringify(e)));
      },
      url,
      this.requestHeader,
      postData
    );
    if ('mtop.alipic.lark.own.order.createTicketOrder' === path) {
      log('创建订单返回 ' + JSON.stringify(res));
    }

    if (!res || !res.data) {
      return false;
    }

    if (res.data.bizCode === '20001' && first && (await this.updateSid(path))) {
      // 登录失效
      return await this.requestPageApi(path, data, false);
    } else if (
      res.data.bizCode === '1002' &&
      res.api === 'mtop.alipic.lark.own.auth.getsidbytid'
    ) {
      throw new Error('账户登录态失效[RETRY]');
    } else if (res.data.bizCode !== '0') {
      log(
        `接口返回失败 参数${JSON.stringify(postData)} ${JSON.stringify(
          res
        )} [${path}]`
      );
      if (res && res.data && res.data.bizMsg) {
        this.requestErr = res.data.bizMsg;
      }
      return false;
    }
    return res.data.bizValue;
  }

  // 更新sid
  async updateSid(path) {
    log('更新sid ' + path);
    this.userSid = '';
    const updateSidRes = await this.requestPageApi(
      'mtop.alipic.lark.own.auth.getSidByTid',
      {
        tid: this.userTid,
      }
    );
    if (!updateSidRes) {
      log('更新sid失败 ' + updateSidRes);
      return false;
    }
    this.userTid = updateSidRes.tid;
    this.userSid = updateSidRes.sid;

    return true;
  }

  async sceenshotOrderDetail(dataJson, cinemaCode) {
    const filePath = './orderJson/' + dataJson.sn + '.json';
    if (!fs.existsSync(filePath)) {
      return false;
    }
    const buffer = fs.readFileSync(filePath);
    const fileContent = buffer.toString();
    if (!fileContent) {
      return false;
    }
    const orderState = JSON.parse(fileContent);
    this.order = orderState;
    const { paramDetail } = orderState.paramDetail;

    // https://h5lark.yuekeyun.com/order/detail.html?wapid=KKYC_H5_PROD_16453_MPSUB&orderId=230929036010X164531107&orderType=TICKET&cinemaLinkId=16453&stamp=1695986824216&spm=a2of6.order_list_page.0.0
    // https://h5lark.yuekeyun.com/order/detail.html?wapid=CTYC_H5_PROD_S_MPS&orderId=230929013030X694531198&orderType=TICKET&cinemaLinkId=69453&stamp=1695986839900&spm=a2of6.order_list_page.0.0
    // https://h5lark.yuekeyun.com/order/detail.html?wapid=XINYINGLIAN_H5_PROD_S_MPS&orderId=230901003011X131521109&orderType=TICKET&cinemaLinkId=13152&stamp=1693563088206&spm=a2of6.order_list_page.0.0
    log(
      '截图订单详情 ' +
        `https://h5lark.yuekeyun.com/order/detail.html?wapid=${cinemaCode}&orderId=${
          paramDetail.orderId
        }&orderType=TICKET&cinemaLinkId=${
          this.cinemaLinkId
        }&stamp=${Date.now()}&spm=a2of6.order_list_page.0.0`
    );
    await this.page.goto(
      `https://h5lark.yuekeyun.com/order/detail.html?wapid=${cinemaCode}&orderId=${
        paramDetail.orderId
      }&orderType=TICKET&cinemaLinkId=${
        this.cinemaLinkId
      }&stamp=${Date.now()}&spm=a2of6.order_list_page.0.0`,
      {
        timeout: 5 * 1000,
        waitUntil: 'networkidle0',
      }
    );
    await sleep(2000);
    const frameElement = await this.page.$('iframe.privacy-dialog');
    if (frameElement) {
      const frame = await frameElement.contentFrame();
      if (frame) {
        const confirmBtn = await frame.$('a.flex-item');
        if (confirmBtn) {
          await confirmBtn.click();
          log('弹窗已自动关闭');
        } else {
          log('弹窗确认按钮未找到');
        }
      } else {
        log('iframe 内容未加载完成');
      }
    } else {
      log('弹窗不存在，跳过操作');
    }

    // 重新加载下页面
    // await this.page.reload();

    // 沉睡3s
    // await this.page.waitForFunction(
    //   'document.querySelector(".qrcode").innerHTML.length>0'
    // );
    // await this.page.waitForSelector('.qrcode', {visible: true});

    const screenshotPath = './screenshot/' + dataJson.sn + '.png';
    await this.page.screenshot({
      path: screenshotPath,
      clip: {
        x: 0,
        y: 0,
        width: 375,
        height: 620,
      },
    });

    // 上传截图
    return await API.uploadScreenshot(screenshotPath);
  }

  // 订单回退
  async orderRollback(dataJson) {
    log('回调订单状态,出票失败');
    // 订单锁定取消
    await API.orderRollback(dataJson.id, dataJson.msg).catch((e) =>
      console.error('取消订单请求异常', e)
    );
    if (!this.order.state) {
      if (this.orderId) {
        // 创建订单成功 付款失败
        const cancelRes = await this.cancelOrder();
        log(`取消订单:${this.orderId} ${JSON.stringify(cancelRes)}`);
      } else if (this.lockOrderId) {
        // 仅仅是座位锁定成功 订单取消锁定
        const unlockRes = await this.unlockSeat();
        log(`解锁座位:${this.lockOrderId} ${JSON.stringify(unlockRes)}`);
      }
    }

    return true;
  }

  // 更新账户场次信息
  async updateShow(task) {
    this.requestErr = '';
    const loginToken = this.accountLoginToken(
      task.cinema_data,
      task.login_token
    );
    try {
      await this.login(loginToken, task.cinemas_code);
    } catch (e) {
      log('登录失败 ' + e.message);
      //   const p = this.page;
      //   this.page = await this.browser.newPage();
      //   await p.close();
      return false;
    }
    // 打开首页
    this.requestbody.channelCode = task['cinemas_code'];
    // 检查登录
    const loginCheck = await this.checkLogin({
      cinemaAccId: task['account_id'],
      cinemaCode: task['cinemas_code'],
      cinemaAccount: task.account,
      cinemaAccountPwd: task.pwd,
    });
    if (!loginCheck) {
      return false;
    }

    const bizData = await this.requestPageApi(
      'mtop.alipic.lark.own.schedule.getSchedules',
      {
        cinemaLinkId: this.cinemaLinkId,
      }
    );
    if (!bizData || !bizData.films) {
      if (this.requestErr && this.requestErr.includes('渠道不存在')) {
        // 关闭影院
        const closeRes = await API.closeCinema(task.cinema_id);
        log(
          `渠道不存在,关闭影院:${task.cinema_id} ${JSON.stringify(closeRes)}`
        );
      }
      return false;
    }

    const filmMap = {};
    const delArr = [];
    for (const film of bizData.films) {
      filmMap[film.filmName] = [];
      for (const date of film.dates) {
        for (const schedule of date.schedules) {
          const showTime = dayjs(Number(schedule.showTime)).format(
            'YYYY-MM-DD HH:mm:ss'
          );
          filmMap[film.filmName].push({
            showTime,
            hallId: schedule.hallId,
            scheduleKey: schedule.scheduleKey,
            showNo: schedule.scheduleId,
            hallName: schedule.hallName,
            displayPrice:
              schedule.displayPrice > 2500
                ? Math.round(schedule.displayPrice * task['vip_cost_ratio'], 2)
                : schedule.displayPrice, // 会员卡价格*系数
            originalStandardPrice: Number(schedule.originalStandardPrice), // 标准价格
            standardPrice: Number(schedule.standardPrice) || 0, // 展示原价
            lowestPrice: Number(schedule.lowestPrice) || 0, // 发行价
            ticketFee: Number(schedule.ticketFee) || 0, // 影院手续费
          });
        }
      }
    }
    // 删除重复场次
    delArr.forEach((obj) => {
      delete filmMap[obj.filmName][obj.showTime];
    });

    await API.updateShowlist(
      task.cinema_id,
      task.account_id,
      task.vip_price_status,
      filmMap
    );

    log(
      `${task['cinema_name']}[${task['cinemas_code']}] 场次数据更新成功[${task.account}] ${task.vip_cost_ratio}`
    );

    // 获取会员卡列表并通知后端
    const cardListRes = await this.requestPageApi(
      'mtop.alipic.lark.own.card.getcardlistbypage',
      {
        cinemaLinkId: this.cinemaLinkId,
        pageNo: 1,
        pageSize: 20,
      }
    );
    if (cardListRes && Array.isArray(cardListRes) && cardListRes.length > 0) {
      // 处理会员卡数据，包含活动信息和状态
      const cardData = cardListRes.map((card) => {
        // 处理会员卡状态
        let status = 5; // 默认未知状态
        if (card.cardStatus === 'T') {
          status = 2; // 过期
        } else if (card.cardStatus === 'N') {
          status = 1; // 正常使用
        }

        return {
          cardNumber: card.cardNumber,
          cardName: card.cardName,
          cardStatus: status,
          activeCardActivityList: card.activeCardActivityList || [],
        };
      });

      // 传输所有会员卡信息到后端
      const noticeRes = await API.noticeCardActivity(
        task.account_id,
        task.cinema_id,
        task.cinema_name,
        task.account,
        cardData
      );
      log(`通知会员卡信息:${JSON.stringify(noticeRes)}`);
    }
  }

  // 更新院线
  async updateCinemas(taskData) {
    this.requestErr = '';
    await this.page.goto(
      `https://h5lark.yuekeyun.com/location/index.html?wapid=ZYSDYG_H5_PROD_16049_MPS`,
      {
        waitUntil: 'networkidle2',
      }
    );

    const bizData = await this.requestPageApi(
      'mtop.alipic.lark.own.cinema.getCinemas',
      {
        channelCode: taskData.cinemas_code,
      }
    );
    if (!bizData || !bizData.cities) {
      return false;
    }

    const cityCinemasMap = [];
    for (const city of bizData.cities) {
      const cityInfo = {
        cityCode: city.cityCode,
        cityName: city.cityName,
      };

      for (const cinema of city.cinemas) {
        cityCinemasMap.push({
          ...cityInfo,
          ...cinema,
        });
      }
    }
    await API.updateCinemasData(
      taskData.id,
      taskData.vip_price_status,
      cityCinemasMap
    );

    log(
      `${taskData['cinemas_name']}[${taskData['cinemas_code']}] 院线数据更新成功`
    );
  }

  // token
  accountLoginToken(ciemaData, userToken) {
    const loginToken = ciemaData || {};
    if (userToken) {
      try {
        loginToken.user = JSON.parse(userToken);
        loginToken.phone = loginToken.user.account.mobile;
        // eslint-disable-next-line no-empty
      } catch (_e) {
        loginToken.user = null;
      }
    }

    if (!loginToken.user || !userToken) {
      loginToken.user = {
        sid: '',
        tid: '',
        account: '',
      };
      loginToken.phone = '';
    }
    return JSON.stringify(loginToken);
  }
}
